/*
 * "Copyright (c) 2012 Universitat Pompeu Fabra.
 * All rights reserved.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose, without fee, and without written agreement is
 * hereby granted, provided that the above copyright notice, the following
 * two paragraphs and the author appear in all copies of this software.
 *
 * IN NO EVENT SHALL UNIVERSITAT POMPEU FABRA BE LIABLE TO ANY PARTY 
 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING 
 * OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF POMPEU FABRA
 * HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * UNIVERSITAT POMPEU FABRA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 * ON AN "AS IS" BASIS, AND UNIVERSITAT POMPEU FABRA HAS NO OBLIGATION TO 
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS."
 */

#include "HbcpParameters.h"
#include "HbcpNetworkPackets.h"
#include "HbcpStatistics.h"

#define RESET_BUFFER 2000

module HbcpNetworkManagerP {
	provides {
		interface Init;
		interface Receive as AppSignal;
	}
	uses {
		interface Init as LinkInit;
		interface Init as ForwardingInit;
		interface Init as RoutingInit;
		
		interface Timer<TMilli> as StartForwarding;
		interface Timer<TMilli> as FirstTimeBuffer;
		interface Timer<TMilli> as TimeBuffer;
		interface Timer<TMilli> as ResetBuffer;
		interface Timer<TMilli> as DiscoveryBuffer;
		
		interface HbcpNetworkManager as RoutingEvent;
		interface HbcpRouting as Routing;
		interface HbcpLink as LinkSend;
		interface HbcpForwarding as Forwarding;
		
		interface Leds;
		interface Random;
		
	}
}
implementation {

	uint8_t nextHopLevel;
	uint8_t hopLevel;
	uint8_t times;
	uint16_t discoveryTimeOffset;
	uint16_t timeBuffer;
	message_t * msg;
	void * payload;
	routingPacket_t * discoveryMsg;
	uint8_t discoveryLen;
	uint16_t packetQueueTime;

	void turnOffLeds() {
		call Leds.led0Off();
		call Leds.led1Off();
		call Leds.led2Off();
	}
	
	command error_t Init.init() {

		stats.packetsRxData = 0;
		stats.packetsTx = 0;
		stats.packetsRxDiscovery = 0;
		stats.packetsAcked = 0;
		stats.extendedTGS = 0;

		call LinkInit.init();
		call ForwardingInit.init();
		call Forwarding.allowTransmission(FALSE);
		call Routing.allowRoutingEngine(TRUE);
		return SUCCESS;
	}

	error_t reset() {

		stats.packetsRxData = 0;
		stats.packetsTx = 0;
		stats.packetsRxDiscovery = 0;
		stats.packetsAcked = 0;
		stats.extendedTGS = 0;

		call ForwardingInit.init();
		call RoutingInit.init();

		call Forwarding.allowTransmission(FALSE);
		call Routing.allowRoutingEngine(TRUE);
		return SUCCESS;
	}

	event void RoutingEvent.routingSignal(routingPacket_t * routingMsg, uint8_t len) {

		uint16_t dataWaitingTime;
		uint16_t collectionTime;
		call Routing.allowRoutingEngine(FALSE);

		//Reset the node
		if(0 == routingMsg->session) {
			call LinkSend.send(AM_BROADCAST_ADDR, routingMsg, len);
			call ResetBuffer.startOneShot(RESET_BUFFER);
			return;
		}
		
		discoveryMsg = routingMsg;
		discoveryLen = len;
		discoveryTimeOffset = routingMsg->coordinationTime;
		
		turnOffLeds();
		call Leds.led0On();
		
		hopLevel = call Routing.getCurrentHop();
		
		//send the discovery packet
		call DiscoveryBuffer.startOneShot(DISCOVERY_SLOT_TIME - discoveryTimeOffset);
		
		signal AppSignal.receive(msg, payload, 1);//len must be differnt from 0
		
		dataWaitingTime = ((MAX_HOPS - hopLevel) * (DISCOVERY_SLOT_TIME));
		collectionTime= (uint16_t) (double)(COLLECTION_SLOT_TIME*0.8);
		timeBuffer = call Random.rand16() % (collectionTime);
		//packetQueueTime=(call Forwarding.getNumPacketsInQueue()*CC2420THROUGHPUT);
		
		/*if(timeBuffer < packetQueueTime){
			timeBuffer=0;
		}
		else{
			timeBuffer-=packetQueueTime;
		}*/
		
		times = 0;
		//HbcpRetryDelay = BASE_RETRY_TIME + (call Random.rand16() % SPREAD_RETRY_TIME);
		//HbcpRetries = ((uint8_t)((((double)(COLLECTION_SLOT_TIME - timeBuffer)) / HbcpRetryDelay)));
		HbcpRetries = 7;
		
		if(HbcpRetries > MAX_RETRIES){
			HbcpRetries = MAX_RETRIES;
		}
		
		
		call FirstTimeBuffer.startOneShot(dataWaitingTime);
		
	}
	
	event void StartForwarding.fired() {
		
		if(times < MAX_HOPS) {
			call Forwarding.allowTransmission(FALSE);
			if((hopLevel + times) % SECTOR_DISTANCE == 1) {	
				call Forwarding.allowTransmission(TRUE);
				call Forwarding.runForwarding();
			}
			times++;
			call TimeBuffer.startOneShot(COLLECTION_SLOT_TIME - timeBuffer);
		}
		else {
			turnOffLeds();
			stats.extendedTGS = 0;
			call Forwarding.cleanQueues();
			call Routing.allowRoutingEngine(TRUE);
		}
	}

	event void TimeBuffer.fired() {
		call Leds.led2Toggle();
		timeBuffer = call Random.rand16() % (COLLECTION_SLOT_TIME);
		packetQueueTime=(call Forwarding.getNumPacketsInQueue()*CC2420THROUGHPUT);
		if(timeBuffer < packetQueueTime){
			timeBuffer=0;
		}	
		else{
			timeBuffer-=packetQueueTime;
		}		
		call Forwarding.allowTransmission(FALSE);
		call StartForwarding.startOneShot(timeBuffer);
	}
	
	event void FirstTimeBuffer.fired(){
		turnOffLeds();
		call StartForwarding.startOneShot(timeBuffer);
	}
	
	event void DiscoveryBuffer.fired(){
		//send discovery packet
		call LinkSend.send(AM_BROADCAST_ADDR, discoveryMsg, discoveryLen);
	}
	
	event void ResetBuffer.fired() {
		reset();
		signal AppSignal.receive(msg, payload, 0);
		turnOffLeds();
	}
}